home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / non-ANSI / c-client / nntpclient.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-07-25  |  43.9 KB  |  1,594 lines

  1. /*
  2.  * Program:    Network News Transfer Protocol (NNTP) client routines
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    5 January 1993
  13.  * Last Edited:    26 July 1993
  14.  *
  15.  * Copyright 1993 by the University of Washington.
  16.  *
  17.  *  Permission to use, copy, modify, and distribute this software and its
  18.  * documentation for any purpose and without fee is hereby granted, provided
  19.  * that the above copyright notices appear in all copies and that both the
  20.  * above copyright notices and this permission notice appear in supporting
  21.  * documentation, and that the name of the University of Washington not be
  22.  * used in advertising or publicity pertaining to distribution of the software
  23.  * without specific, written prior permission.  This software is made
  24.  * available "as is", and
  25.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  26.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  27.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  28.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  29.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  30.  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  31.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN
  32.  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  33.  *
  34.  */
  35.  
  36.  
  37. #include "mail.h"
  38. #include "osdep.h"
  39. #include <ctype.h>
  40. #include <stdio.h>
  41. #include <netdb.h>
  42. #include <errno.h>
  43. extern int errno;        /* just in case */
  44. #include <sys/file.h>
  45. #include <sys/stat.h>
  46. #include "smtp.h"
  47. #define search_t Search_t
  48. #include "news.h"
  49. #undef search_t
  50. #undef LOCAL
  51. #include "nntp.h"
  52. #include "nntpclient.h"
  53. #include "rfc822.h"
  54. #include "misc.h"
  55.  
  56. /* NNTP mail routines */
  57.  
  58.  
  59. /* Driver dispatch used by MAIL */
  60.  
  61. DRIVER nntpdriver = {
  62.   "nntp",            /* driver name */
  63.   (DRIVER *) NIL,        /* next driver */
  64.   nntp_valid,            /* mailbox is valid for us */
  65.   nntp_parameters,        /* manipulate parameters */
  66.   nntp_find,            /* find mailboxes */
  67.   nntp_find_bboards,        /* find bboards */
  68.   nntp_find_all,        /* find all mailboxes */
  69.   nntp_find_all_bboards,    /* find all bboards */
  70.   nntp_subscribe,        /* subscribe to mailbox */
  71.   nntp_unsubscribe,        /* unsubscribe from mailbox */
  72.   nntp_subscribe_bboard,    /* subscribe to bboard */
  73.   nntp_unsubscribe_bboard,    /* unsubscribe from bboard */
  74.   nntp_create,            /* create mailbox */
  75.   nntp_delete,            /* delete mailbox */
  76.   nntp_rename,            /* rename mailbox */
  77.   nntp_mopen,            /* open mailbox */
  78.   nntp_close,            /* close mailbox */
  79.   nntp_fetchfast,        /* fetch message "fast" attributes */
  80.   nntp_fetchflags,        /* fetch message flags */
  81.   nntp_fetchstructure,        /* fetch message envelopes */
  82.   nntp_fetchheader,        /* fetch message header only */
  83.   nntp_fetchtext,        /* fetch message body only */
  84.   nntp_fetchbody,        /* fetch message body section */
  85.   nntp_setflag,            /* set message flag */
  86.   nntp_clearflag,        /* clear message flag */
  87.   nntp_search,            /* search for message based on criteria */
  88.   nntp_ping,            /* ping mailbox to see if still alive */
  89.   nntp_check,            /* check for new messages */
  90.   nntp_expunge,            /* expunge deleted messages */
  91.   nntp_copy,            /* copy messages to another mailbox */
  92.   nntp_move,            /* move messages to another mailbox */
  93.   nntp_append,            /* append string message to mailbox */
  94.   nntp_gc            /* garbage collect stream */
  95. };
  96.  
  97.                 /* prototype stream */
  98. MAILSTREAM nntpproto = {&nntpdriver};
  99.  
  100. /* NNTP mail validate mailbox
  101.  * Accepts: mailbox name
  102.  * Returns: our driver if name is valid, NIL otherwise
  103.  */
  104.  
  105. DRIVER *nntp_valid (name)
  106.     char *name;
  107. {
  108.                 /* must be bboard */
  109.   return *name == '*' ? mail_valid_net (name,&nntpdriver,NIL,NIL) : NIL;
  110. }
  111.  
  112.  
  113. /* News manipulate driver parameters
  114.  * Accepts: function code
  115.  *        function-dependent value
  116.  * Returns: function-dependent return value
  117.  */
  118.  
  119. void *nntp_parameters (function,value)
  120.     long function;
  121.     void *value;
  122. {
  123.   fatal ("Invalid nntp_parameters function");
  124.   return NIL;
  125. }
  126.  
  127. /* NNTP mail find list of mailboxes
  128.  * Accepts: mail stream
  129.  *        pattern to search
  130.  */
  131.  
  132. void nntp_find (stream,pat)
  133.     MAILSTREAM *stream;
  134.     char *pat;
  135. {
  136.   /* Always a no-op */
  137. }
  138.  
  139.  
  140. /* NNTP mail find list of bboards
  141.  * Accepts: mail stream
  142.  *        pattern to search
  143.  */
  144.  
  145. void nntp_find_bboards (stream,pat)
  146.     MAILSTREAM *stream;
  147.     char *pat;
  148. {
  149.   void *s = NIL;
  150.   char *t,*u,*bbd,*patx,tmp[MAILTMPLEN];
  151.   if (stream) {            /* use .newsrc if a stream given */
  152.                 /* begin with a host specification? */
  153.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  154.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  155.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  156.       strcpy (tmp,pat);        /* copy host name */
  157.       bbd = tmp + (patx - pat);    /* where we write the bboards */
  158.     }
  159.     else {            /* no host specification */
  160.       bbd = tmp;        /* no prefix */
  161.       patx = pat;        /* use entire specification */
  162.     }
  163.     while (t = news_read (&s)) if (u = strchr (t,':')) {
  164.       *u = '\0';        /* tie off at end of name */
  165.       if (pmatch (t,patx)) {    /* pattern match */
  166.     strcpy (bbd,t);        /* write newsgroup name after prefix */
  167.     mm_bboard (tmp);
  168.       }
  169.     }
  170.   }
  171. }
  172.  
  173. /* NNTP mail find list of all mailboxes
  174.  * Accepts: mail stream
  175.  *        pattern to search
  176.  */
  177.  
  178. void nntp_find_all (stream,pat)
  179.     MAILSTREAM *stream;
  180.     char *pat;
  181. {
  182.   /* Always a no-op */
  183. }
  184.  
  185.  
  186. /* NNTP mail find list of all bboards
  187.  * Accepts: mail stream
  188.  *        pattern to search
  189.  */
  190.  
  191. void nntp_find_all_bboards (stream,pat)
  192.     MAILSTREAM *stream;
  193.     char *pat;
  194. {
  195.   char *s,*t,*bbd,*patx,tmp[MAILTMPLEN];
  196.                 /* use .newsrc if a stream given */
  197.   if (stream && LOCAL && LOCAL->nntpstream) {
  198.                 /* begin with a host specification? */
  199.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  200.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  201.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  202.       strcpy (tmp,pat);        /* copy host name */
  203.       bbd = tmp + (patx - pat);    /* where we write the bboards */
  204.     }
  205.     else {            /* no host specification */
  206.       bbd = tmp;        /* no prefix */
  207.       patx = pat;        /* use entire specification */
  208.     }
  209.                 /* ask server for all active newsgroups */
  210.     if (!(smtp_send (LOCAL->nntpstream,"LIST","ACTIVE") == NNTPGLIST)) return;
  211.                 /* process data until we see final dot */
  212.     while ((s = tcp_getline (LOCAL->nntpstream->tcpstream)) && *s != '.') {
  213.                 /* tie off after newsgroup name */
  214.       if (t = strchr (s,' ')) *t = '\0';
  215.       if (pmatch (s,patx)) {    /* report to main program if have match */
  216.     strcpy (bbd,s);        /* write newsgroup name after prefix */
  217.     mm_bboard (tmp);
  218.       }
  219.       fs_give ((void **) &s);    /* clean up */
  220.     }
  221.   }
  222. }
  223.  
  224. /* NNTP mail subscribe to mailbox
  225.  * Accepts: mail stream
  226.  *        mailbox to add to subscription list
  227.  * Returns: T on success, NIL on failure
  228.  */
  229.  
  230. long nntp_subscribe (stream,mailbox)
  231.     MAILSTREAM *stream;
  232.     char *mailbox;
  233. {
  234.   return NIL;            /* never valid for NNTP */
  235. }
  236.  
  237.  
  238. /* NNTP mail unsubscribe to mailbox
  239.  * Accepts: mail stream
  240.  *        mailbox to delete from subscription list
  241.  * Returns: T on success, NIL on failure
  242.  */
  243.  
  244. long nntp_unsubscribe (stream,mailbox)
  245.     MAILSTREAM *stream;
  246.     char *mailbox;
  247. {
  248.   return NIL;            /* never valid for NNTP */
  249. }
  250.  
  251.  
  252. /* NNTP mail subscribe to bboard
  253.  * Accepts: mail stream
  254.  *        bboard to add to subscription list
  255.  * Returns: T on success, NIL on failure
  256.  */
  257.  
  258. long nntp_subscribe_bboard (stream,mailbox)
  259.     MAILSTREAM *stream;
  260.     char *mailbox;
  261. {
  262.   char *s = strchr (mailbox,'}');
  263.   return s ? news_subscribe_bboard (stream,s+1) : NIL;
  264. }
  265.  
  266.  
  267. /* NNTP mail unsubscribe to bboard
  268.  * Accepts: mail stream
  269.  *        bboard to delete from subscription list
  270.  * Returns: T on success, NIL on failure
  271.  */
  272.  
  273. long nntp_unsubscribe_bboard (stream,mailbox)
  274.     MAILSTREAM *stream;
  275.     char *mailbox;
  276. {
  277.   char *s = strchr (mailbox,'}');
  278.   return s ? news_unsubscribe_bboard (stream,s+1) : NIL;
  279. }
  280.  
  281. /* NNTP mail create mailbox
  282.  * Accepts: mail stream
  283.  *        mailbox name to create
  284.  * Returns: T on success, NIL on failure
  285.  */
  286.  
  287. long nntp_create (stream,mailbox)
  288.     MAILSTREAM *stream;
  289.     char *mailbox;
  290. {
  291.   return NIL;            /* never valid for NNTP */
  292. }
  293.  
  294.  
  295. /* NNTP mail delete mailbox
  296.  *        mailbox name to delete
  297.  * Returns: T on success, NIL on failure
  298.  */
  299.  
  300. long nntp_delete (stream,mailbox)
  301.     MAILSTREAM *stream;
  302.     char *mailbox;
  303. {
  304.   return NIL;            /* never valid for NNTP */
  305. }
  306.  
  307.  
  308. /* NNTP mail rename mailbox
  309.  * Accepts: mail stream
  310.  *        old mailbox name
  311.  *        new mailbox name
  312.  * Returns: T on success, NIL on failure
  313.  */
  314.  
  315. long nntp_rename (stream,old,new)
  316.     MAILSTREAM *stream;
  317.     char *old;
  318.     char *new;
  319. {
  320.   return NIL;            /* never valid for NNTP */
  321. }
  322.  
  323. /* NNTP mail open
  324.  * Accepts: stream to open
  325.  * Returns: stream on success, NIL on failure
  326.  */
  327.  
  328. MAILSTREAM *nntp_mopen (stream)
  329.     MAILSTREAM *stream;
  330. {
  331.   long i,j,k;
  332.   long nmsgs = 0;
  333.   long recent = 0;
  334.   long unseen = 0;
  335.   char c = NIL,*s,*t,tmp[MAILTMPLEN];
  336.   NETMBX mb;
  337.   void *sdb = NIL;
  338.   struct hostent *host_name;
  339.   void *tcpstream;
  340.   SMTPSTREAM *nstream = NIL;
  341.                 /* return prototype for OP_PROTOTYPE call */
  342.   if (!stream) return &nntpproto;
  343.   mail_valid_net_parse (stream->mailbox,&mb);
  344.   if (!lhostn) {        /* have local host yet? */
  345.     gethostname(tmp,MAILTMPLEN);/* get local host name */
  346.     lhostn = cpystr ((host_name = gethostbyname (tmp)) ?
  347.              host_name->h_name : tmp);
  348.   }
  349.   if (!*mb.mailbox) strcpy (mb.mailbox,"general");
  350.   if (LOCAL) {            /* if recycle stream, see if changing hosts */
  351.     if (strcmp (lcase (mb.host),lcase (strcpy (tmp,LOCAL->host)))) {
  352.       sprintf (tmp,"Closing connection to %s",LOCAL->host);
  353.       if (!stream->silent) mm_log (tmp,(long) NIL);
  354.     }
  355.     else {            /* same host, preserve NNTP connection */
  356.       sprintf (tmp,"Reusing connection to %s",LOCAL->host);
  357.       if (!stream->silent) mm_log (tmp,(long) NIL);
  358.       nstream = LOCAL->nntpstream;
  359.       LOCAL->nntpstream = NIL;    /* keep nntp_close() from punting it */
  360.     }
  361.     nntp_close (stream);    /* do close action */
  362.     stream->dtb = &nntpdriver;/* reattach this driver */
  363.     mail_free_cache (stream);    /* clean up cache */
  364.   }
  365.  
  366.                 /* open NNTP now if not already open */
  367.   if (!nstream && (tcpstream = tcp_open (mb.host,mb.port ?
  368.                      (long) mb.port : NNTPTCPPORT))) {
  369.     nstream = (SMTPSTREAM *) fs_get (sizeof (SMTPSTREAM));
  370.     nstream->tcpstream = tcpstream;
  371.     nstream->debug = stream->debug;
  372.     nstream->reply = NIL;
  373.                 /* get NNTP greeting */
  374.     if (smtp_reply (nstream) == NNTPGREET)
  375.       mm_log (nstream->reply + 4,(long) NIL);
  376.     else {            /* oops */
  377.       mm_log (nstream->reply,ERROR);
  378.       smtp_close (nstream);    /* punt stream */
  379.       nstream = NIL;
  380.     }
  381.   }
  382.   if (nstream) {        /* now try to open newsgroup */
  383.     if ((!stream->halfopen) &&    /* open the newsgroup if not halfopen */
  384.     ((smtp_send (nstream,"GROUP",mb.mailbox) != NNTPGOK) ||
  385.      ((nmsgs = strtol (nstream->reply + 4,&s,10)) < 0) ||
  386.      ((i = strtol (s,&s,10)) < 0) || ((j = strtol (s,&s,10)) < 0))) {
  387.       mm_log (nstream->reply,ERROR);
  388.       smtp_close (nstream);    /* punt stream */
  389.       nstream = NIL;
  390.       return NIL;
  391.     }
  392.                 /* newsgroup open, instantiate local data */
  393.     stream->local = fs_get (sizeof (NNTPLOCAL));
  394.     LOCAL->nntpstream = nstream;
  395.     LOCAL->dirty = NIL;        /* no update to .newsrc needed yet */
  396.                 /* copy host and newsgroup name */
  397.     LOCAL->host = cpystr (mb.host);
  398.     LOCAL->name = cpystr (mb.mailbox);
  399.     stream->sequence++;        /* bump sequence number */
  400.     stream->readonly = T;    /* make sure higher level knows readonly */
  401.     if (stream->halfopen) {    /* no caches or buffers for half-open */
  402.       LOCAL->number = NIL;
  403.       LOCAL->header = LOCAL->body = NIL;
  404.       LOCAL->seen = NIL;
  405.       LOCAL->buf = NIL;
  406.     }
  407.  
  408.     else {            /* try to get list of valid numbers */
  409.                 /* make temporary buffer */
  410.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  411.       if (smtp_send (nstream,"LISTGROUP",mb.mailbox) == NNTPGOK) {
  412.                 /* create number map */
  413.     LOCAL->number = (unsigned long *) fs_get(nmsgs*sizeof (unsigned long));
  414.                 /* initialize c-client/NNTP map */
  415.     for (i = 0; (i<nmsgs) && (s = tcp_getline (nstream->tcpstream)); ++i) {
  416.       LOCAL->number[i] = atol (s);
  417.       fs_give ((void **) &s);
  418.     }
  419.                 /* get and flush the dot-line */
  420.     if ((s = tcp_getline (nstream->tcpstream)) && (*s == '.'))
  421.       fs_give ((void **) &s);
  422.     else {            /* lose */
  423.       mm_log ("NNTP article listing protocol failure",ERROR);
  424.       nntp_close (stream);    /* do close action */
  425.     }
  426.       }
  427.       else {            /* a vanilla NNTP server, barf */
  428.                 /* any holes in sequence? */
  429.     if (nmsgs != (k = (j - i) + 1)) {
  430.       sprintf (tmp,"[HOLES] %ld non-existant message(s)",k-nmsgs);
  431.       mm_notify (stream,tmp,(long) NIL);
  432.       nmsgs = k;        /* sure are, set new message count */
  433.     }
  434.                 /* create number map */
  435.     LOCAL->number = (unsigned long *) fs_get(nmsgs*sizeof (unsigned long));
  436.                 /* initialize c-client/NNTP map */
  437.     for (k = 0; k < nmsgs; ++k) LOCAL->number[k] = i + k;
  438.       }
  439.                 /* create caches */
  440.       LOCAL->header = (char **) fs_get (nmsgs * sizeof (char *));
  441.       LOCAL->body = (char **) fs_get (nmsgs * sizeof (char *));
  442.       LOCAL->seen = (char *) fs_get (nmsgs * sizeof (char));
  443.                 /* initialize per-message cache */
  444.       for (i = 0; i < nmsgs; ++i) {
  445.     LOCAL->header[i] = LOCAL->body[i] = NIL;
  446.     LOCAL->seen[i] = NIL;
  447.       }
  448.  
  449.                 /* notify upper level that messages exist */
  450.       mail_exists (stream,nmsgs);
  451.       i = 0;            /* nothing scanned yet */
  452.       while ((t = news_read (&sdb)) && (s = strpbrk (t,":!")) && (c = *s)) {
  453.     *s++ = '\0';        /* tie off newsgroup name, point to data */
  454.     if (strcmp (t,LOCAL->name)) s = NIL;
  455.     else break;        /* found it! */
  456.       }
  457.       if (s) {            /* newsgroup found? */
  458.     if (c == '!') mm_log ("Not subscribed to that newsgroup",WARN);
  459.     while (*s && i < nmsgs){/* process until run out of messages or list */
  460.       j = strtol (s,&s,10);    /* start of possible range */
  461.                 /* other end of range */
  462.       k = (*s == '-') ? strtol (++s,&s,10) : j;
  463.                 /* skip messages before this range */
  464.       while ((LOCAL->number[i] < j) && (i < nmsgs)) {
  465.         if (!unseen) unseen = i + 1;
  466.         i++;
  467.       }
  468.       while ((LOCAL->number[i] >= j) && (LOCAL->number[i] <= k) &&
  469.          (i < nmsgs)){    /* mark messages within the range as seen */
  470.         LOCAL->seen[i++] = T;
  471.         mail_elt (stream,i)->deleted = T;
  472.       }
  473.       if (*s == ',') s++;    /* skip past comma */
  474.       else if (*s) {    /* better not be anything else then */
  475.         mm_log ("Bogus syntax in news state file",ERROR);
  476.         break;        /* give up fast!! */
  477.       }
  478.     }
  479.       }
  480.       else mm_log ("No state for newsgroup found, reading as new",WARN);
  481.       if (t) fs_give (&sdb);    /* free up database if necessary */
  482.       while (i++ < nmsgs) {    /* mark all remaining messages as new */
  483.     mail_elt (stream,i)->recent = T;
  484.     ++recent;        /* count another recent message */
  485.       }
  486.       if (unseen) {        /* report first unseen message */
  487.     sprintf (tmp,"[UNSEEN] %ld is first unseen message",unseen);
  488.     mm_notify (stream,tmp,(long) NIL);
  489.       }
  490.                 /* notify upper level about recent */
  491.       mail_recent (stream,recent);
  492.                 /* notify if empty bboard */
  493.       if (!(stream->nmsgs || stream->silent))
  494.     mm_log ("Newsgroup is empty",WARN);
  495.     }
  496.   }
  497.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  498. }
  499.  
  500. /* NNTP mail close
  501.  * Accepts: MAIL stream
  502.  */
  503.  
  504. void nntp_close (stream)
  505.     MAILSTREAM *stream;
  506. {
  507.   if (LOCAL) {            /* only if a file is open */
  508.     nntp_check (stream);    /* dump final checkpoint */
  509.     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
  510.     if (LOCAL->host) fs_give ((void **) &LOCAL->host);
  511.     nntp_gc (stream,GC_TEXTS);    /* free local cache */
  512.     if (LOCAL->number) fs_give ((void **) &LOCAL->number);
  513.     if (LOCAL->header) fs_give ((void **) &LOCAL->header);
  514.     if (LOCAL->body) fs_give ((void **) &LOCAL->body);
  515.     if (LOCAL->seen) fs_give ((void **) &LOCAL->seen);
  516.                 /* free local scratch buffer */
  517.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  518.                 /* close NNTP connection */
  519.     if (LOCAL->nntpstream) smtp_close (LOCAL->nntpstream);
  520.                 /* nuke the local data */
  521.     fs_give ((void **) &stream->local);
  522.     stream->dtb = NIL;        /* log out the DTB */
  523.   }
  524. }
  525.  
  526. /* NNTP mail fetch fast information
  527.  * Accepts: MAIL stream
  528.  *        sequence
  529.  */
  530.  
  531. void nntp_fetchfast (stream,sequence)
  532.     MAILSTREAM *stream;
  533.     char *sequence;
  534. {
  535.   return;            /* no-op for local mail */
  536. }
  537.  
  538.  
  539. /* NNTP mail fetch flags
  540.  * Accepts: MAIL stream
  541.  *        sequence
  542.  */
  543.  
  544. void nntp_fetchflags (stream,sequence)
  545.     MAILSTREAM *stream;
  546.     char *sequence;
  547. {
  548.   return;            /* no-op for local mail */
  549. }
  550.  
  551. /* NNTP mail fetch envelope
  552.  * Accepts: MAIL stream
  553.  *        message # to fetch
  554.  *        pointer to return body
  555.  * Returns: envelope of this message, body returned in body value
  556.  *
  557.  * Fetches the "fast" information as well
  558.  */
  559.  
  560. ENVELOPE *nntp_fetchstructure (stream,msgno,body)
  561.     MAILSTREAM *stream;
  562.     long msgno;
  563.     BODY **body;
  564. {
  565.   char *h,*t;
  566.   LONGCACHE *lelt;
  567.   ENVELOPE **env;
  568.   STRING bs;
  569.   BODY **b;
  570.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  571.   if (stream->scache) {        /* short cache */
  572.     if (msgno != stream->msgno){/* flush old poop if a different message */
  573.       mail_free_envelope (&stream->env);
  574.       mail_free_body (&stream->body);
  575.     }
  576.     stream->msgno = msgno;
  577.     env = &stream->env;        /* get pointers to envelope and body */
  578.     b = &stream->body;
  579.   }
  580.   else {            /* long cache */
  581.     lelt = mail_lelt (stream,msgno);
  582.     env = &lelt->env;        /* get pointers to envelope and body */
  583.     b = &lelt->body;
  584.   }
  585.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  586.     mail_free_envelope (env);    /* flush old envelope and body */
  587.     mail_free_body (b);
  588.     h = nntp_fetchheader (stream,msgno);
  589.     t = body ? nntp_fetchtext_work (stream,msgno) : "dummy";
  590.                 /* calculate message size */
  591.     elt->rfc822_size = strlen (h) + strlen (t);
  592.     INIT (&bs,mail_string,(void *) t,strlen (t));
  593.                 /* parse envelope and body */
  594.     rfc822_parse_msg (env,body ? b : NIL,h,strlen (h),&bs,lhostn,LOCAL->buf);
  595.                 /* parse date */
  596.     if (*env && (*env)->date) mail_parse_date (elt,(*env)->date);
  597.     if (!elt->month) mail_parse_date (elt,"01-JAN-1969 00:00:00 GMT");
  598.   }
  599.   if (body) *body = *b;        /* return the body */
  600.   return *env;            /* return the envelope */
  601. }
  602.  
  603. /* NNTP mail fetch message header
  604.  * Accepts: MAIL stream
  605.  *        message # to fetch
  606.  * Returns: message header in RFC822 format
  607.  */
  608.  
  609. char *nntp_fetchheader (stream,msgno)
  610.     MAILSTREAM *stream;
  611.     long msgno;
  612. {
  613.   char tmp[MAILTMPLEN];
  614.   long m = msgno - 1;
  615.   if (!LOCAL->header[m]) {    /* fetch header if don't have already */
  616.     sprintf (tmp,"%ld",LOCAL->number[m]);
  617.     if (smtp_send (LOCAL->nntpstream,"HEAD",tmp) == NNTPHEAD)
  618.       LOCAL->header[m] = nntp_slurp (stream);
  619.     else {            /* failed, mark as deleted */
  620.       LOCAL->seen[m] = T;
  621.       mail_elt (stream,msgno)->deleted = T;
  622.     }
  623.   }
  624.   return LOCAL->header[m] ? LOCAL->header[m] : "";
  625. }
  626.  
  627.  
  628. /* NNTP mail fetch message text (only)
  629.     body only;
  630.  * Accepts: MAIL stream
  631.  *        message # to fetch
  632.  * Returns: message text in RFC822 format
  633.  */
  634.  
  635. char *nntp_fetchtext (stream,msgno)
  636.     MAILSTREAM *stream;
  637.     long msgno;
  638. {
  639.   long m = msgno - 1;
  640.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  641.   elt->seen = T;        /* mark as seen */
  642.   return nntp_fetchtext_work (stream,msgno);
  643. }
  644.  
  645.  
  646. /* NNTP mail fetch message text work
  647.  * Accepts: MAIL stream
  648.  *        message # to fetch
  649.  * Returns: message text in RFC822 format
  650.  */
  651.  
  652. char *nntp_fetchtext_work (stream,msgno)
  653.     MAILSTREAM *stream;
  654.     long msgno;
  655. {
  656.   char tmp[MAILTMPLEN];
  657.   long m = msgno - 1;
  658.   if (!LOCAL->body[m]) {    /* fetch body if don't have already */
  659.     sprintf (tmp,"%ld",LOCAL->number[m]);
  660.     if (smtp_send (LOCAL->nntpstream,"BODY",tmp) == NNTPBODY)
  661.       LOCAL->body[m] = nntp_slurp (stream);
  662.     else {            /* failed, mark as deleted */
  663.       LOCAL->seen[m] = T;
  664.       mail_elt (stream,msgno)->deleted = T;
  665.     }
  666.   }
  667.   return LOCAL->body[m] ? LOCAL->body[m] : "";
  668. }
  669.  
  670. /* NNTP mail slurp NNTP dot-terminated text
  671.  * Accepts: MAIL stream
  672.  * Returns: text
  673.  */
  674.  
  675. char *nntp_slurp (stream)
  676.     MAILSTREAM *stream;
  677. {
  678.   char *s,*t;
  679.   unsigned long i;
  680.   unsigned long bufpos = 0;
  681.   while (s = tcp_getline (LOCAL->nntpstream->tcpstream)) {
  682.     if (*s == '.') {        /* possible end of text? */
  683.       if (s[1]) t = s + 1;    /* pointer to true start of line */
  684.       else break;        /* end of data */
  685.     }
  686.     else t = s;            /* want the entire line */
  687.                 /* ensure have enough room */
  688.     if (LOCAL->buflen < (bufpos + (i = strlen (t)) + 5))
  689.       fs_resize ((void **) &LOCAL->buf,LOCAL->buflen += (MAXMESSAGESIZE + 1));
  690.                 /* copy the text */
  691.     strncpy (LOCAL->buf + bufpos,t,i);
  692.     bufpos += i;        /* set new buffer position */
  693.     LOCAL->buf[bufpos++] = '\015';
  694.     LOCAL->buf[bufpos++] = '\012';
  695.     fs_give ((void **) &s);    /* free the line */
  696.   }
  697.   LOCAL->buf[bufpos++] = '\015';/* add final newline */
  698.   LOCAL->buf[bufpos++] = '\012';
  699.   LOCAL->buf[bufpos++] = '\0';    /* tie off string with NUL */
  700.   return cpystr (LOCAL->buf);    /* return copy of collected string */
  701. }
  702.  
  703. /* NNTP fetch message body as a structure
  704.  * Accepts: Mail stream
  705.  *        message # to fetch
  706.  *        section specifier
  707.  *        pointer to length
  708.  * Returns: pointer to section of message body
  709.  */
  710.  
  711. char *nntp_fetchbody (stream,m,s,len)
  712.     MAILSTREAM *stream;
  713.     long m;
  714.     char *s;
  715.     unsigned long *len;
  716. {
  717.   BODY *b;
  718.   PART *pt;
  719.   unsigned long i;
  720.   char *base;
  721.   unsigned long offset = 0;
  722.   MESSAGECACHE *elt = mail_elt (stream,m);
  723.                 /* make sure have a body */
  724.   if (!(nntp_fetchstructure (stream,m,&b) && b && s && *s &&
  725.     ((i = strtol (s,&s,10)) > 0) &&
  726.     (base = nntp_fetchtext_work (stream,m))))
  727.     return NIL;
  728.   do {                /* until find desired body part */
  729.                 /* multipart content? */
  730.     if (b->type == TYPEMULTIPART) {
  731.       pt = b->contents.part;    /* yes, find desired part */
  732.       while (--i && (pt = pt->next));
  733.       if (!pt) return NIL;    /* bad specifier */
  734.                 /* note new body, check valid nesting */
  735.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  736.       offset = pt->offset;    /* get new offset */
  737.     }
  738.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  739.                 /* need to go down further? */
  740.     if (i = *s) switch (b->type) {
  741.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  742.       offset = b->contents.msg.offset;
  743.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  744.     case TYPEMULTIPART:        /* multipart, get next section */
  745.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  746.     default:            /* bogus subpart specification */
  747.       return NIL;
  748.     }
  749.   } while (i);
  750.                 /* lose if body bogus */
  751.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  752.   elt->seen = T;        /* mark as seen */
  753.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  754.               b->size.ibytes,b->encoding);
  755. }
  756.  
  757. /* NNTP mail set flag
  758.  * Accepts: MAIL stream
  759.  *        sequence
  760.  *        flag(s)
  761.  */
  762.  
  763. void nntp_setflag (stream,sequence,flag)
  764.     MAILSTREAM *stream;
  765.     char *sequence;
  766.     char *flag;
  767. {
  768.   MESSAGECACHE *elt;
  769.   long i;
  770.   short f = nntp_getflags (stream,flag);
  771.   if (!f) return;        /* no-op if no flags to modify */
  772.                 /* get sequence and loop on it */
  773.   if (mail_sequence (stream,sequence)) for (i = 0; i < stream->nmsgs; i++)
  774.     if ((elt = mail_elt (stream,i + 1))->sequence) {
  775.       if (f&fSEEN) elt->seen=T;    /* set all requested flags */
  776.       if (f&fDELETED) {        /* deletion also purges the cache */
  777.     elt->deleted = T;    /* mark deleted */
  778.     if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  779.     if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  780.     if (!LOCAL->seen[i]) LOCAL->seen[i] = LOCAL->dirty = T;
  781.       }
  782.       if (f&fFLAGGED) elt->flagged = T;
  783.       if (f&fANSWERED) elt->answered = T;
  784.     }
  785. }
  786.  
  787.  
  788. /* NNTP mail clear flag
  789.  * Accepts: MAIL stream
  790.  *        sequence
  791.  *        flag(s)
  792.  */
  793.  
  794. void nntp_clearflag (stream,sequence,flag)
  795.     MAILSTREAM *stream;
  796.     char *sequence;
  797.     char *flag;
  798. {
  799.   MESSAGECACHE *elt;
  800.   long i;
  801.   short f = nntp_getflags (stream,flag);
  802.   if (!f) return;        /* no-op if no flags to modify */
  803.                 /* get sequence and loop on it */
  804.   if (mail_sequence (stream,sequence)) for (i = 0; i < stream->nmsgs; i++)
  805.     if ((elt = mail_elt (stream,i + 1))->sequence) {
  806.                 /* clear all requested flags */
  807.       if (f&fSEEN) elt->seen = NIL;
  808.       if (f&fDELETED) {
  809.     elt->deleted = NIL;    /* undelete */
  810.     if (LOCAL->seen[i]) {    /* if marked in newsrc */
  811.       LOCAL->seen[i] = NIL;    /* unmark it now */
  812.       LOCAL->dirty = T;    /* mark stream as dirty */
  813.     }
  814.       }
  815.       if (f&fFLAGGED) elt->flagged = NIL;
  816.       if (f&fANSWERED) elt->answered = NIL;
  817.     }
  818. }
  819.  
  820. /* NNTP mail search for messages
  821.  * Accepts: MAIL stream
  822.  *        search criteria
  823.  */
  824.  
  825. void nntp_search (stream,criteria)
  826.     MAILSTREAM *stream;
  827.     char *criteria;
  828. {
  829.   long i,n;
  830.   char *d;
  831.   search_t f;
  832.                 /* initially all searched */
  833.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  834.                 /* get first criterion */
  835.   if (criteria && (criteria = strtok (criteria," "))) {
  836.                 /* for each criterion */
  837.     for (; criteria; (criteria = strtok (NIL," "))) {
  838.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  839.       switch (*ucase (criteria)) {
  840.       case 'A':            /* possible ALL, ANSWERED */
  841.     if (!strcmp (criteria+1,"LL")) f = nntp_search_all;
  842.     else if (!strcmp (criteria+1,"NSWERED")) f = nntp_search_answered;
  843.     break;
  844.       case 'B':            /* possible BCC, BEFORE, BODY */
  845.     if (!strcmp (criteria+1,"CC"))
  846.       f = nntp_search_string (nntp_search_bcc,&d,&n);
  847.     else if (!strcmp (criteria+1,"EFORE"))
  848.       f = nntp_search_date (nntp_search_before,&n);
  849.     else if (!strcmp (criteria+1,"ODY"))
  850.       f = nntp_search_string (nntp_search_body,&d,&n);
  851.     break;
  852.       case 'C':            /* possible CC */
  853.     if (!strcmp (criteria+1,"C"))
  854.       f = nntp_search_string (nntp_search_cc,&d,&n);
  855.     break;
  856.       case 'D':            /* possible DELETED */
  857.     if (!strcmp (criteria+1,"ELETED")) f = nntp_search_deleted;
  858.     break;
  859.       case 'F':            /* possible FLAGGED, FROM */
  860.     if (!strcmp (criteria+1,"LAGGED")) f = nntp_search_flagged;
  861.     else if (!strcmp (criteria+1,"ROM"))
  862.       f = nntp_search_string (nntp_search_from,&d,&n);
  863.     break;
  864.       case 'K':            /* possible KEYWORD */
  865.     if (!strcmp (criteria+1,"EYWORD"))
  866.       f = nntp_search_flag (nntp_search_keyword,&d);
  867.     break;
  868.       case 'N':            /* possible NEW */
  869.     if (!strcmp (criteria+1,"EW")) f = nntp_search_new;
  870.     break;
  871.  
  872.       case 'O':            /* possible OLD, ON */
  873.     if (!strcmp (criteria+1,"LD")) f = nntp_search_old;
  874.     else if (!strcmp (criteria+1,"N"))
  875.       f = nntp_search_date (nntp_search_on,&n);
  876.     break;
  877.       case 'R':            /* possible RECENT */
  878.     if (!strcmp (criteria+1,"ECENT")) f = nntp_search_recent;
  879.     break;
  880.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  881.     if (!strcmp (criteria+1,"EEN")) f = nntp_search_seen;
  882.     else if (!strcmp (criteria+1,"INCE"))
  883.       f = nntp_search_date (nntp_search_since,&n);
  884.     else if (!strcmp (criteria+1,"UBJECT"))
  885.       f = nntp_search_string (nntp_search_subject,&d,&n);
  886.     break;
  887.       case 'T':            /* possible TEXT, TO */
  888.     if (!strcmp (criteria+1,"EXT"))
  889.       f = nntp_search_string (nntp_search_text,&d,&n);
  890.     else if (!strcmp (criteria+1,"O"))
  891.       f = nntp_search_string (nntp_search_to,&d,&n);
  892.     break;
  893.       case 'U':            /* possible UN* */
  894.     if (criteria[1] == 'N') {
  895.       if (!strcmp (criteria+2,"ANSWERED")) f = nntp_search_unanswered;
  896.       else if (!strcmp (criteria+2,"DELETED")) f = nntp_search_undeleted;
  897.       else if (!strcmp (criteria+2,"FLAGGED")) f = nntp_search_unflagged;
  898.       else if (!strcmp (criteria+2,"KEYWORD"))
  899.         f = nntp_search_flag (nntp_search_unkeyword,&d);
  900.       else if (!strcmp (criteria+2,"SEEN")) f = nntp_search_unseen;
  901.     }
  902.     break;
  903.       default:            /* we will barf below */
  904.     break;
  905.       }
  906.       if (!f) {            /* if can't determine any criteria */
  907.     sprintf (LOCAL->buf,"Unknown search criterion: %.80s",criteria);
  908.     mm_log (LOCAL->buf,ERROR);
  909.     return;
  910.       }
  911.                 /* run the search criterion */
  912.       for (i = 1; i <= stream->nmsgs; ++i)
  913.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  914.       mail_elt (stream,i)->searched = NIL;
  915.     }
  916.                 /* report search results to main program */
  917.     for (i = 1; i <= stream->nmsgs; ++i)
  918.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  919.   }
  920. }
  921.  
  922. /* NNTP mail ping mailbox
  923.  * Accepts: MAIL stream
  924.  * Returns: T if stream alive, else NIL
  925.  */
  926.  
  927. long nntp_ping (stream)
  928.     MAILSTREAM *stream;
  929. {
  930.   /* Kludge alert: SMTPSOFTFATAL is 421 which is used in NNTP to mean ``No
  931.    * next article in this group''.  Hopefully, no NNTP server will choke on
  932.    * a bogus command. */
  933.   return (smtp_send (LOCAL->nntpstream,"PING","PONG") != SMTPSOFTFATAL);
  934. }
  935.  
  936.  
  937. /* NNTP mail check mailbox
  938.  * Accepts: MAIL stream
  939.  */
  940.  
  941. void nntp_check (stream)
  942.     MAILSTREAM *stream;
  943. {
  944.   int fd;
  945.   long i,j,k;
  946.   char *s;
  947.   char tmp[MAILTMPLEN];
  948.   struct stat sbuf;
  949.   struct iovec iov[3];
  950.   if (!LOCAL->dirty) return;    /* never do if no updates */
  951.   *LOCAL->buf = '\n';        /* header to make for easier searches */
  952.                 /* open .newsrc file */
  953.   if ((fd = open (NEWSRC,O_RDWR|O_CREAT,0600)) < 0) {
  954.     mm_log ("Can't update news state",ERROR);
  955.     return;
  956.   }
  957.   flock (fd,LOCK_EX);        /* wait for exclusive access */
  958.   fstat (fd,&sbuf);        /* get size of data */
  959.                 /* ensure enough room */
  960.   if (sbuf.st_size >= (LOCAL->buflen + 1)) {
  961.                 /* fs_resize does an unnecessary copy */
  962.     fs_give ((void **) &LOCAL->buf);
  963.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size + 1) + 1);
  964.   }
  965.   *LOCAL->buf = '\n';        /* slurp the silly thing in */
  966.   read (fd,iov[0].iov_base = LOCAL->buf + 1,iov[0].iov_len = sbuf.st_size);
  967.                 /* tie off file */
  968.   LOCAL->buf[sbuf.st_size + 1] = '\0';
  969.                 /* make backup file */
  970.   strcat (strcpy (tmp,myhomedir ()),"/.oldnewsrc");
  971.   if ((i = open (tmp,O_WRONLY|O_CREAT,0600)) >= 0) {
  972.     write (i,LOCAL->buf + 1,sbuf.st_size);
  973.     close (i);
  974.   }
  975.  
  976.                 /* find as subscribed newsgroup */
  977.   sprintf (tmp,"\n%s:",LOCAL->name);
  978.   if (s = strstr (LOCAL->buf,tmp)) s += strlen (tmp);
  979.   else {            /* find as unsubscribed newsgroup */
  980.     sprintf (tmp,"\n%s!",LOCAL->name);
  981.     if (s = strstr (LOCAL->buf,tmp)) s += strlen (tmp);
  982.   }
  983.   iov[2].iov_base = "";        /* dummy in case no third block */
  984.   iov[2].iov_len = 0;
  985.   if (s) {            /* found existing, calculate prefix length */
  986.     iov[0].iov_len = s - (LOCAL->buf + 1);
  987.     if (s = strchr (s+1,'\n')) {/* find suffix */
  988.       iov[2].iov_base = ++s;    /* suffix base and length */
  989.       iov[2].iov_len = sbuf.st_size - (s - (LOCAL->buf + 1));
  990.     }
  991.     s = tmp;            /* pointer to dump sequence numbers */
  992.   }
  993.   else {            /* not found, append as unsubscribed group */
  994.     sprintf (tmp,"%s!",LOCAL->name);
  995.     s = tmp + strlen (tmp);    /* point to end of string */
  996.   }
  997.   *s++ = ' ';            /* leading space */
  998.   *s = '\0';            /* go through list */
  999.   for (i = 0,j = 1,k = 0; i < stream->nmsgs; ++i) {
  1000.     if (LOCAL->seen[i]) {    /* seen message? */
  1001.       k = LOCAL->number[i];    /* this is the top of the current range */
  1002.       if (j == 0) j = k;    /* if no range in progress, start one */
  1003.     }
  1004.     else if (j != 0) {        /* unread message, ending a range */
  1005.                 /* calculate end of range */
  1006.       if (k = LOCAL->number[i] - 1) {
  1007.                 /* dump range */
  1008.     sprintf (s,(j == k) ? "%d," : "%d-%d,",j,k);
  1009.     s += strlen (s);    /* find end of string */
  1010.       }
  1011.       j = 0;            /* no more range in progress */
  1012.     }
  1013.   }
  1014.   if (j) {            /* dump trailing range */
  1015.     sprintf (s,(j == k) ? "%d" : "%d-%d",j,k);
  1016.     s += strlen (s);        /* find end of string */
  1017.   }
  1018.   else if (s[-1] == ',') s--;    /* prepare to patch out any trailing comma */
  1019.   *s++ = '\n';            /* trailing newline */
  1020.   iov[1].iov_base = tmp;    /* this group text */
  1021.   iov[1].iov_len = s - tmp;    /* length of the text */
  1022.   lseek (fd,0,L_SET);        /* go to beginning of file */
  1023.   writev (fd,iov,iov[2].iov_len ? 3 : 2);
  1024.   ftruncate (fd,iov[0].iov_len + iov[1].iov_len + iov[2].iov_len);
  1025.   flock (fd,LOCK_UN);        /* unlock the file */
  1026.   close (fd);            /* flush .newsrc file */
  1027. }
  1028.  
  1029. /* NNTP mail expunge mailbox
  1030.  * Accepts: MAIL stream
  1031.  */
  1032.  
  1033. void nntp_expunge (stream)
  1034.     MAILSTREAM *stream;
  1035. {
  1036.   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  1037. }
  1038.  
  1039.  
  1040. /* NNTP mail copy message(s)
  1041.     s;
  1042.  * Accepts: MAIL stream
  1043.  *        sequence
  1044.  *        destination mailbox
  1045.  * Returns: T if copy successful, else NIL
  1046.  */
  1047.  
  1048. long nntp_copy (stream,sequence,mailbox)
  1049.     MAILSTREAM *stream;
  1050.     char *sequence;
  1051.     char *mailbox;
  1052. {
  1053.   char tmp[MAILTMPLEN],lock[MAILTMPLEN];
  1054.   struct stat sbuf;
  1055.   char c,*h,*t,*s,*s1;
  1056.   int fd;
  1057.   long i,m;
  1058.   long ret = T;
  1059.   MESSAGECACHE *elt;
  1060.                 /* get sequence to do */
  1061.   if (!mail_sequence (stream,sequence)) return NIL;
  1062.                 /* get destination mailbox */
  1063.   if ((fd = bezerk_lock (bezerk_file (tmp,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  1064.               S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  1065.     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  1066.     mm_log (LOCAL->buf,ERROR);
  1067.     return NIL;
  1068.   }
  1069.   mm_critical (stream);        /* go critical */
  1070.   fstat (fd,&sbuf);        /* get current file size */
  1071.  
  1072.                 /* write all requested messages to mailbox */
  1073.   for (m = 1; ret && (m <= stream->nmsgs); m++)
  1074.     if ((elt = mail_elt (stream,m))->sequence) {
  1075.                 /* get message header and text sans CR */
  1076.       h = cpystr (nntp_fetchheader (stream,m));
  1077.       for (s = h,s1 = h; c = *s++;) if (c != '\015') *s1++ = c;
  1078.       *s1++ = '\0';        /* tie off header string */
  1079.       t = cpystr (nntp_fetchtext (stream,m));
  1080.       for (s = t,s1 = t; c = *s++;) if (c != '\015') *s1++ = c;
  1081.       *s1++ = '\0';        /* tie off text string */
  1082.                 /* make sure we have a date */
  1083.       nntp_fetchstructure (stream,m,NIL);
  1084.                 /* build From header line */
  1085.       strcpy (tmp,"From somebody ");
  1086.                 /* use Path: contents if have it */
  1087.       if (((*(s1 = h) == 'P' && h[1] == 'a' && h[2] == 't' && h[3] == 'h' &&
  1088.         h[4] == ':' && h[5] == ' ') || (s1 = strstr (h,"\nPath: "))) &&
  1089.       (s = strchr (s1 += 6,'\n'))) {
  1090.     strncpy (tmp+5,s1,i = s - s1);
  1091.     tmp[5 + i] = ' ';
  1092.     s = tmp + 6 + i;    /* where to write date */
  1093.       }
  1094.       else s = tmp + strlen (tmp);
  1095.       mail_cdate (s,elt);    /* write date */
  1096.       sprintf (tmp + strlen (tmp),"Article %lu of %s\n",
  1097.            LOCAL->number[m - 1],LOCAL->name);
  1098.       if ((write (fd,tmp,strlen (tmp)) < 0) || (write (fd,h,strlen (h)) < 0) ||
  1099.       (write (fd,t,strlen (t)) < 0) || (write (fd,"\n",1) < 0)) {
  1100.     sprintf (LOCAL->buf,"Message %ld copy failed: %s",m,strerror (errno));
  1101.     mm_log (LOCAL->buf,ERROR);
  1102.     ftruncate (fd,sbuf.st_size);
  1103.     ret = NIL;        /* give up */
  1104.       }
  1105.       fs_give ((void **) &h);    /* flush strings */
  1106.       fs_give ((void **) &t);
  1107.     }
  1108.   fsync (fd);            /* force out the update */
  1109.   bezerk_unlock (fd,NIL,lock);    /* unlock and close mailbox */
  1110.   mm_nocritical (stream);    /* release critical */
  1111.   return ret;            /* return whether or not succeeded */
  1112. }
  1113.  
  1114. /* NNTP mail move message(s)
  1115.     s;
  1116.  * Accepts: MAIL stream
  1117.  *        sequence
  1118.  *        destination mailbox
  1119.  * Returns: T if move successful, else NIL
  1120.  */
  1121.  
  1122. long nntp_move (stream,sequence,mailbox)
  1123.     MAILSTREAM *stream;
  1124.     char *sequence;
  1125.     char *mailbox;
  1126. {
  1127.   long i;
  1128.   MESSAGECACHE *elt;
  1129.   if (!(mail_sequence (stream,sequence) &&
  1130.     nntp_copy (stream,sequence,mailbox))) return NIL;
  1131.                 /* delete all requested messages */
  1132.   for (i = 1; i <= stream->nmsgs; i++)
  1133.     if ((elt = mail_elt (stream,i))->sequence) {
  1134.       elt->deleted = T;        /* mark message deleted */
  1135.       LOCAL->dirty = T;        /* mark mailbox as dirty */
  1136.       LOCAL->seen[i - 1] = T;    /* and seen for .newsrc update */
  1137.     }
  1138.   return T;
  1139. }
  1140.  
  1141.  
  1142. /* NNTP mail append message from stringstruct
  1143.  * Accepts: MAIL stream
  1144.  *        destination mailbox
  1145.  *        stringstruct of messages to append
  1146.  * Returns: T if append successful, else NIL
  1147.  */
  1148.  
  1149. long nntp_append (stream,mailbox,message)
  1150.     MAILSTREAM *stream;
  1151.     char *mailbox;
  1152.     STRING *message;
  1153. {
  1154.   mm_log ("Append not valid for NNTP",ERROR);
  1155.   return NIL;
  1156. }
  1157.  
  1158.  
  1159. /* NNTP garbage collect stream
  1160.  * Accepts: Mail stream
  1161.  *        garbage collection flags
  1162.  */
  1163.  
  1164. void nntp_gc (stream,gcflags)
  1165.     MAILSTREAM *stream;
  1166.     long gcflags;
  1167. {
  1168.   unsigned long i;
  1169.   if (!stream->halfopen)     /* never on half-open stream */
  1170.     if (gcflags & GC_TEXTS)    /* garbage collect texts? */
  1171.                 /* flush texts from cache */
  1172.       for (i = 0; i < stream->nmsgs; i++) {
  1173.     if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  1174.     if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  1175.       }
  1176. }
  1177.  
  1178. /* Internal routines */
  1179.  
  1180.  
  1181. /* Parse flag list
  1182.  * Accepts: MAIL stream
  1183.  *        flag list as a character string
  1184.  * Returns: flag command list
  1185.  */
  1186.  
  1187. short nntp_getflags (stream,flag)
  1188.     MAILSTREAM *stream;
  1189.     char *flag;
  1190. {
  1191.   char *t;
  1192.   short f = 0;
  1193.   short i,j;
  1194.   if (flag && *flag) {        /* no-op if no flag string */
  1195.                 /* check if a list and make sure valid */
  1196.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  1197.       mm_log ("Bad flag list",ERROR);
  1198.       return NIL;
  1199.     }
  1200.                 /* copy the flag string w/o list construct */
  1201.     strncpy (LOCAL->buf,flag+i,(j = strlen (flag) - (2*i)));
  1202.     LOCAL->buf[j] = '\0';
  1203.     t = ucase (LOCAL->buf);    /* uppercase only from now on */
  1204.  
  1205.     while (*t) {        /* parse the flags */
  1206.       if (*t == '\\') {        /* system flag? */
  1207.     switch (*++t) {        /* dispatch based on first character */
  1208.     case 'S':        /* possible \Seen flag */
  1209.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  1210.       t += 4;        /* skip past flag name */
  1211.       break;
  1212.     case 'D':        /* possible \Deleted flag */
  1213.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  1214.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  1215.       t += 7;        /* skip past flag name */
  1216.       break;
  1217.     case 'F':        /* possible \Flagged flag */
  1218.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  1219.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  1220.       t += 7;        /* skip past flag name */
  1221.       break;
  1222.     case 'A':        /* possible \Answered flag */
  1223.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  1224.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  1225.       t += 8;        /* skip past flag name */
  1226.       break;
  1227.     default:        /* unknown */
  1228.       i = 0;
  1229.       break;
  1230.     }
  1231.                 /* add flag to flags list */
  1232.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  1233.     else {            /* bitch about bogus flag */
  1234.       mm_log ("Unknown system flag",ERROR);
  1235.       return NIL;
  1236.     }
  1237.       }
  1238.       else {            /* no user flags yet */
  1239.     mm_log ("Unknown flag",ERROR);
  1240.     return NIL;
  1241.       }
  1242.     }
  1243.   }
  1244.   return f;
  1245. }
  1246.  
  1247. /* Search support routines
  1248.  * Accepts: MAIL stream
  1249.  *        message number
  1250.  *        pointer to additional data
  1251.  *        pointer to temporary buffer
  1252.  * Returns: T if search matches, else NIL
  1253.  */
  1254.  
  1255. char nntp_search_all (stream,msgno,d,n)
  1256.     MAILSTREAM *stream;
  1257.     long msgno;
  1258.     char *d;
  1259.     long n;
  1260. {
  1261.   return T;            /* ALL always succeeds */
  1262. }
  1263.  
  1264.  
  1265. char nntp_search_answered (stream,msgno,d,n)
  1266.     MAILSTREAM *stream;
  1267.     long msgno;
  1268.     char *d;
  1269.     long n;
  1270. {
  1271.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1272. }
  1273.  
  1274.  
  1275. char nntp_search_deleted (stream,msgno,d,n)
  1276.     MAILSTREAM *stream;
  1277.     long msgno;
  1278.     char *d;
  1279.     long n;
  1280. {
  1281.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1282. }
  1283.  
  1284.  
  1285. char nntp_search_flagged (stream,msgno,d,n)
  1286.     MAILSTREAM *stream;
  1287.     long msgno;
  1288.     char *d;
  1289.     long n;
  1290. {
  1291.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1292. }
  1293.  
  1294.  
  1295. char nntp_search_keyword (stream,msgno,d,n)
  1296.     MAILSTREAM *stream;
  1297.     long msgno;
  1298.     char *d;
  1299.     long n;
  1300. {
  1301.   return NIL;            /* keywords not supported yet */
  1302. }
  1303.  
  1304.  
  1305. char nntp_search_new (stream,msgno,d,n)
  1306.     MAILSTREAM *stream;
  1307.     long msgno;
  1308.     char *d;
  1309.     long n;
  1310. {
  1311.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1312.   return (elt->recent && !elt->seen) ? T : NIL;
  1313. }
  1314.  
  1315. char nntp_search_old (stream,msgno,d,n)
  1316.     MAILSTREAM *stream;
  1317.     long msgno;
  1318.     char *d;
  1319.     long n;
  1320. {
  1321.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1322. }
  1323.  
  1324.  
  1325. char nntp_search_recent (stream,msgno,d,n)
  1326.     MAILSTREAM *stream;
  1327.     long msgno;
  1328.     char *d;
  1329.     long n;
  1330. {
  1331.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1332. }
  1333.  
  1334.  
  1335. char nntp_search_seen (stream,msgno,d,n)
  1336.     MAILSTREAM *stream;
  1337.     long msgno;
  1338.     char *d;
  1339.     long n;
  1340. {
  1341.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1342. }
  1343.  
  1344.  
  1345. char nntp_search_unanswered (stream,msgno,d,n)
  1346.     MAILSTREAM *stream;
  1347.     long msgno;
  1348.     char *d;
  1349.     long n;
  1350. {
  1351.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1352. }
  1353.  
  1354.  
  1355. char nntp_search_undeleted (stream,msgno,d,n)
  1356.     MAILSTREAM *stream;
  1357.     long msgno;
  1358.     char *d;
  1359.     long n;
  1360. {
  1361.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1362. }
  1363.  
  1364.  
  1365. char nntp_search_unflagged (stream,msgno,d,n)
  1366.     MAILSTREAM *stream;
  1367.     long msgno;
  1368.     char *d;
  1369.     long n;
  1370. {
  1371.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1372. }
  1373.  
  1374.  
  1375. char nntp_search_unkeyword (stream,msgno,d,n)
  1376.     MAILSTREAM *stream;
  1377.     long msgno;
  1378.     char *d;
  1379.     long n;
  1380. {
  1381.   return T;            /* keywords not supported yet */
  1382. }
  1383.  
  1384.  
  1385. char nntp_search_unseen (stream,msgno,d,n)
  1386.     MAILSTREAM *stream;
  1387.     long msgno;
  1388.     char *d;
  1389.     long n;
  1390. {
  1391.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1392. }
  1393.  
  1394. char nntp_search_before (stream,msgno,d,n)
  1395.     MAILSTREAM *stream;
  1396.     long msgno;
  1397.     char *d;
  1398.     long n;
  1399. {
  1400.   return (char) (nntp_msgdate (stream,msgno) < n);
  1401. }
  1402.  
  1403.  
  1404. char nntp_search_on (stream,msgno,d,n)
  1405.     MAILSTREAM *stream;
  1406.     long msgno;
  1407.     char *d;
  1408.     long n;
  1409. {
  1410.   return (char) (nntp_msgdate (stream,msgno) == n);
  1411. }
  1412.  
  1413.  
  1414. char nntp_search_since (stream,msgno,d,n)
  1415.     MAILSTREAM *stream;
  1416.     long msgno;
  1417.     char *d;
  1418.     long n;
  1419. {
  1420.                 /* everybody interprets "since" as .GE. */
  1421.   return (char) (nntp_msgdate (stream,msgno) >= n);
  1422. }
  1423.  
  1424.  
  1425. unsigned long nntp_msgdate (stream,msgno)
  1426.     MAILSTREAM *stream;
  1427.     long msgno;
  1428. {
  1429.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1430.                 /* get date if don't have it yet */
  1431.   if (!elt->day) nntp_fetchstructure (stream,msgno,NIL);
  1432.   return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
  1433. }
  1434.  
  1435.  
  1436. char nntp_search_body (stream,msgno,d,n)
  1437.     MAILSTREAM *stream;
  1438.     long msgno;
  1439.     char *d;
  1440.     long n;
  1441. {
  1442.   char *t = nntp_fetchtext_work (stream,msgno);
  1443.   return (t && search (t,(unsigned long) strlen (t),d,n));
  1444. }
  1445.  
  1446.  
  1447. char nntp_search_subject (stream,msgno,d,n)
  1448.     MAILSTREAM *stream;
  1449.     long msgno;
  1450.     char *d;
  1451.     long n;
  1452. {
  1453.   char *t = nntp_fetchstructure (stream,msgno,NIL)->subject;
  1454.   return t ? search (t,strlen (t),d,n) : NIL;
  1455. }
  1456.  
  1457.  
  1458. char nntp_search_text (stream,msgno,d,n)
  1459.     MAILSTREAM *stream;
  1460.     long msgno;
  1461.     char *d;
  1462.     long n;
  1463. {
  1464.   char *t = nntp_fetchheader (stream,msgno);
  1465.   return (t && search (t,strlen (t),d,n)) ||
  1466.     nntp_search_body (stream,msgno,d,n);
  1467. }
  1468.  
  1469. char nntp_search_bcc (stream,msgno,d,n)
  1470.     MAILSTREAM *stream;
  1471.     long msgno;
  1472.     char *d;
  1473.     long n;
  1474. {
  1475.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1476.                 /* get text for address */
  1477.   rfc822_write_address (LOCAL->buf,nntp_fetchstructure(stream,msgno,NIL)->bcc);
  1478.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1479. }
  1480.  
  1481.  
  1482. char nntp_search_cc (stream,msgno,d,n)
  1483.     MAILSTREAM *stream;
  1484.     long msgno;
  1485.     char *d;
  1486.     long n;
  1487. {
  1488.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1489.                 /* get text for address */
  1490.   rfc822_write_address (LOCAL->buf,nntp_fetchstructure (stream,msgno,NIL)->cc);
  1491.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1492. }
  1493.  
  1494.  
  1495. char nntp_search_from (stream,msgno,d,n)
  1496.     MAILSTREAM *stream;
  1497.     long msgno;
  1498.     char *d;
  1499.     long n;
  1500. {
  1501.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1502.                 /* get text for address */
  1503.   rfc822_write_address (LOCAL->buf,
  1504.             nntp_fetchstructure (stream,msgno,NIL)->from);
  1505.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1506. }
  1507.  
  1508.  
  1509. char nntp_search_to (stream,msgno,d,n)
  1510.     MAILSTREAM *stream;
  1511.     long msgno;
  1512.     char *d;
  1513.     long n;
  1514. {
  1515.   LOCAL->buf[0] = '\0';            /* initially empty string */
  1516.                 /* get text for address */
  1517.   rfc822_write_address (LOCAL->buf,nntp_fetchstructure (stream,msgno,NIL)->to);
  1518.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1519. }
  1520.  
  1521. /* Search parsers */
  1522.  
  1523.  
  1524. /* Parse a date
  1525.  * Accepts: function to return
  1526.  *        pointer to date integer to return
  1527.  * Returns: function to return
  1528.  */
  1529.  
  1530. search_t nntp_search_date (f,n)
  1531.     search_t f;
  1532.     long *n;
  1533. {
  1534.   long i;
  1535.   char *s;
  1536.   MESSAGECACHE elt;
  1537.                 /* parse the date and return fn if OK */
  1538.   return (nntp_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  1539.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  1540. }
  1541.  
  1542. /* Parse a flag
  1543.  * Accepts: function to return
  1544.  *        pointer to string to return
  1545.  * Returns: function to return
  1546.  */
  1547.  
  1548. search_t nntp_search_flag (f,d)
  1549.     search_t f;
  1550.     char **d;
  1551. {
  1552.                 /* get a keyword, return if OK */
  1553.   return (*d = strtok (NIL," ")) ? f : NIL;
  1554. }
  1555.  
  1556.  
  1557. /* Parse a string
  1558.  * Accepts: function to return
  1559.  *        pointer to string to return
  1560.  *        pointer to string length to return
  1561.  * Returns: function to return
  1562.  */
  1563.  
  1564. search_t nntp_search_string (f,d,n)
  1565.     search_t f;
  1566.     char **d;
  1567.     long *n;
  1568. {
  1569.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1570.   if (c) {            /* better be an argument */
  1571.     switch (*c) {        /* see what the argument is */
  1572.     case '\0':            /* catch bogons */
  1573.     case ' ':
  1574.       return NIL;
  1575.     case '"':            /* quoted string */
  1576.       if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
  1577.     return NIL;
  1578.       break;
  1579.     case '{':            /* literal string */
  1580.       *n = strtol (c+1,&c,10);    /* get its length */
  1581.       if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
  1582.       *n > strlen (*d = c)) return NIL;
  1583.       c[*n] = '\255';        /* write new delimiter */
  1584.       strtok (c,"\255");    /* reset the strtok mechanism */
  1585.       break;
  1586.     default:            /* atomic string */
  1587.       *n = strlen (*d = strtok (c," "));
  1588.       break;
  1589.     }
  1590.     return f;
  1591.   }
  1592.   else return NIL;
  1593. }
  1594.